<?php

namespace backend\modules\admin\models;

use Yii;
use yii\db\Query;
use common\components\Tree;
use common\components\Helper;
use common\components\JsonValidator;
use backend\modules\admin\components\Configs;
use yii\helpers\Inflector;

/**
 * This is the model class for table "menu".
 *
 * @property integer $id Menu id(autoincrement)
 * @property string $name Menu name
 * @property string $menutype
 * @property string $alias
 * @property string $note
 * @property string $path
 * @property string $type
 * @property int $published
 * @property integer $parent Menu parent
 * @property integer $level
 * @property string $route Route for this menu
 * @property string $request
 * @property string $params
 * @property int $browserNav
 * @property string $language Language for this menu
 * @property int $lft
 * @property int $rgt
 * @property integer $ordering Menu order
 * @property integer $home
 * @property string $data Extra information for this menu
 *
 * @property Menu $menuParent Menu parent
 * @property Menu[] $menus Menu children
 *
 * @author Misbahul D Munir <misbahuldmunir@gmail.com>
 * @since 1.0
 */
class Menu extends \yii\db\ActiveRecord
{

    public $parent_name;
    public $attrLabel = [];
    public $setAlias = true;
    public $setPath = true;

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return Configs::instance()->menuTable;
    }

    /**
     * @inheritdoc
     */
    public static function getDb()
    {
        if (Configs::instance()->db !== null) {
            return Configs::instance()->db;
        } else {
            return parent::getDb();
        }
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name', 'menutype', 'type'], 'required'],
            [['parent_name'], 'in',
                'range' => $this->menuNames,
                'message' => 'Menu "{value}" not found.'],
            [['parent', 'data'], 'default'],
            [['parent'], 'filterParent', 'when' => function () {
                return !$this->isNewRecord;
            }],
            [['ordering', 'published', 'level', 'lft', 'rgt', 'browserNav', 'home'], 'integer'],
            [['route'], 'required', 'when' => function ($model) {
                return $model->type === 'url';
            }, 'whenClient' => 'function(attribute, value){return $(\'#menu-type\').val() === \'url\';}'],
            [['route'], 'url', 'when' => function ($model) {
                return $model->type === 'url';
            }, 'whenClient' => 'function(attribute, value){return $(\'#menu-type\').val() === \'url\';}'],
            [['request', 'params', 'data'], 'filter', 'filter' => function ($value) {
                if (is_array($value)) {
                    $value = array_filter($value, function ($v) {
                        return $v !== '';
                    });
                    return json_encode($value);
                } else {
                    return $value;
                }
            }],
            [['name'], 'string', 'max' => 128],
            [['menutype'], 'string', 'max' => 24],
            [['alias'], 'string', 'max' => 200],
            [['type'], 'string', 'max' => 16],
            [['route', 'note'], 'string', 'max' => 255],
            [['language'], 'string', 'max' => 7],
            [['route', 'request', 'params'], 'default', 'value' => ''],
            [['ordering'], 'default', 'value' => 0],
            [['alias'], 'filter', 'filter' => function ($value) {
                if ($this->setAlias){
                    return empty($value) ? Inflector::slug($this->name) : $value;
                }else{
                    return $value;
                }
            }],
            [['path'], 'filter', 'filter' => function ($value) {
                if ($this->setPath){
                    if ($this->parent) {
                        $parentMenu = self::findOne($this->parent);
                        return trim($parentMenu->path, '/') . '/' . $this->alias;
                    } else {
                        return $this->alias;
                    }
                }else{
                    return $value;
                }
            }]
        ];
    }

    /**
     * Use to loop detected.
     */
    public function filterParent()
    {
        $parent = $this->parent;
        $db = static::getDb();
        $query = (new Query)->select(['parent'])
            ->from(static::tableName())
            ->where('[[id]]=:id');
        while ($parent) {
            if ($this->id == $parent) {
                $this->addError('parent_name', 'Loop detected.');
                return;
            }
            $parent = $query->params([':id' => $parent])->scalar($db);
        }
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        $attrLabel = [
            'id' => Yii::t('rbac-admin', 'ID'),
            'name' => Yii::t('rbac-admin', 'NAME'),
            'menutype' => Yii::t('rbac-admin', 'MENU_TYPE'),
            'home' => Yii::t('rbac-admin', 'DEFAULT_HOME'),
            'alias' => Yii::t('rbac-admin', 'ALIAS'),
            'note' => Yii::t('rbac-admin', 'NOTE'),
            'path' => Yii::t('rbac-admin', 'PATH'),
            'type' => Yii::t('rbac-admin', 'MENU_ITEM_TYPES'),
            'published' => Yii::t('rbac-admin', 'STATUS'),
            'parent' => Yii::t('rbac-admin', 'PARENT'),
            'parent_name' => Yii::t('rbac-admin', 'PARENT_NAME'),
            'level' => Yii::t('rbac-admin', 'LEVEL'),
            'route' => Yii::t('rbac-admin', $this->type == 'url' ? 'LINK_URL' : 'ROUTE'),
            'browserNav' => Yii::t('rbac-admin', 'BROWSER_NAV'),
            'language' => Yii::t('rbac-admin', 'LANGUAGE'),
            'ordering' => Yii::t('rbac-admin', 'ORDER'),
            'request' => Yii::t('rbac-admin','REQUEST'),
            'params' => Yii::t('rbac-admin', 'PARAMS'),
            'data' => Yii::t('rbac-admin', 'DATA'),
            'search' => Yii::t('common', 'SEARCH')
        ];

        return array_merge($attrLabel, $this->attrLabel);
    }

    /**
     * Get menu parent
     * @return yii\db\ActiveQuery
     */
    public function getMenuParent()
    {
        return $this->hasOne(Menu::class, ['id' => 'parent']);
    }

    /**
     * Get menu children
     * @return yii\db\ActiveQuery
     */
    public function getMenus()
    {
        return $this->hasMany(Menu::class, ['parent' => 'id']);
    }

    /**
     * Get menu type
     * @return yii\db\ActiveQuery
     */
    public function getMenuTypes()
    {
        return $this->hasOne(MenuTypes::class, ['menutype' => 'menutype']);
    }

    /**
     * set Level
     * @param int $parent
     */
    public function setLevel($parent)
    {
        $this->level = $parent === null ? 0 : self::findOne($parent)->level + 1;
    }

    /**
     * @inheritdoc
     */
    public function save($runValidation = true, $attributeNames = null)
    {
        if (!$this->validate()) {
            return false;
        }
        $this->setLevel($this->parent);

        return parent::save($runValidation, $attributeNames);
    }

    private static $_routes;

    /**
     * Get saved routes.
     * @return array
     */
    public static function getSavedRoutes()
    {
        if (self::$_routes === null) {
            self::$_routes = [];
            foreach (Configs::authManager()->getPermissions() as $name => $value) {
                $advanced = Configs::instance()->advanced;
                $prefix = $advanced ? '@' : '/';
                if ($name[0] === $prefix && substr($name, -1) != '*') {
                    self::$_routes[] = $name;
                }
            }
        }
        return self::$_routes;
    }

    public static function getMenuSource()
    {
        $menus = static::find()
            ->select(['id', 'name', 'route', 'parent', 'menutype'])
            ->where(['published' => 1])
            ->andWhere(['<>', 'type', 'separator'])
            ->asArray()
            ->all();

        $menuList = [];

        foreach ($menus as $menu) {
            $menu['name'] = static::getNameLang($menu['id']);
            $menu['parent_name'] = static::getNameLang($menu['parent']);
            unset($menu['parent']);
            $menuList[$menu['menutype']][] = $menu;
        }

        return $menuList;
    }

    /**
     * {@inheritdoc}
     */
    public function afterSave($insert, $changedAttributes)
    {
        parent::afterSave($insert, $changedAttributes);
        $cache = Yii::$app->cache;
        if ($cache->exists('menutype-' . $this->menutype) || $cache->exists('menutype-all')) {
            $cache->delete('menutype-' . $this->menutype);
            $cache->delete('menutype-all');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function afterDelete()
    {
        parent::afterDelete();
        $cache = Yii::$app->cache;
        if ($cache->exists('menutype-' . $this->menutype)) {
            $cache->delete('menutype-' . $this->menutype);
            $cache->delete('menutype-all');
        }
    }

    /**
     * 设置菜单关系排序值
     */
    public function setLftRgt()
    {
        $menus = self::find()->orderBy('ordering')->asArray()->all();
        $newMenus = Helper::resetLftRgt($menus);
        foreach ($newMenus as $item) {
            $menu = self::findOne($item['id']);
            $menu->lft = $item['lft'];
            $menu->rgt = $item['rgt'];
            $menu->setAlias = false;
            $menu->setPath = false;
            $menu->save();
        }
    }

    /**
     * 获取菜单名称数组
     * @return array
     */
    protected function getMenuNames()
    {
        $menus = static::find()
            ->select(['name', 'data'])
            ->where(['published' => 1])
            ->andWhere(['<>', 'type', 'separator'])
            ->asArray()
            ->all();
        foreach ($menus as &$menu) {
            $menuData = json_decode($menu['data'], true);
            if (!is_null($menuData) && isset($menuData['lang-cat'])) {
                $menu['name'] = Yii::t($menuData['lang-cat'], $menu['name']);
            }
            unset($menu['data']);
        }
        return array_map(function ($name) {
            return $name['name'];
        }, $menus);
    }

    /**
     * 获取本地化的菜单名称
     * @param int $id
     * @return string
     */
    public function getNameLang($id)
    {
        if (!(is_numeric($id) && is_int((int)$id))) {
            return '';
        }
        $menu = static::findOne($id);

        if (empty($menu->data)) {
            return $menu->name;
        }

        $data = json_decode($menu->data, true);
        if ($data['lang-cat']) {
            return Yii::t($data['lang-cat'], $menu->name);
        }

        return $menu->data;
    }

    public function getParentMenus($menuType = '', $menuId = 0)
    {
        $data[] = [
            'value' => null,
            'label' => Yii::t('rbac-admin', 'MENUS_ITEM_ROOT')
        ];
        if ($menuType) {
            $menus = self::find()
                ->select(['id', 'name', 'parent', 'menutype', 'level', 'data'])
                ->where(['menutype' => $menuType, 'published' => 1])
                ->andWhere(['<>', 'type', 'separator'])
                ->andWhere(['<>', 'id', $menuId])
                ->orderBy('lft')
                ->asArray()
                ->all();
            $tree = new Tree($menus, ['parent_key' => 'parent']);
            $items = $tree->makeTreeForHtml();
            foreach ($items as $item) {
                $itemData = json_decode($item['data'], true);
                $langCat = $itemData['lang-cat'] ?? false;
                $prefix = str_repeat(' - ', $item['level']);
                $data[] = [
                    'value' => $item['id'],
                    'label' => $prefix . ($langCat ? Yii::t($langCat, $item['name']) : $item['name'])
                ];
            }
        }
        return $data;
    }
}
